//------------------------------------------------------------
//  Filename: camera_master.v
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2016-11-27 12:39
//  Description: 
//   
//  Copyright (C) 2014, YRBD, Inc.                           
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
module CAMERA_MASTER #( 
    parameter integer C_M_AXI_BURST_LEN      = 64,
    parameter integer C_M_AXI_ID_WIDTH       = 1,
    parameter integer C_M_AXI_ADDR_WIDTH     = 32,
    parameter integer C_M_AXI_DATA_WIDTH     = 32,
    parameter integer C_M_AXI_AWUSER_WIDTH   = 0,
    parameter integer C_M_AXI_WUSER_WIDTH    = 0,
    parameter integer C_M_AXI_BUSER_WIDTH    = 0
)( 
    //************* Write channel ***********
    input  wire                               CAMERA_AXI_ACLK,
    input  wire                               CAMERA_AXI_ARESETN,
    output wire [C_M_AXI_ID_WIDTH-1 : 0     ] CAMERA_AXI_AWID,
    output reg  [C_M_AXI_ADDR_WIDTH-1 : 0   ] CAMERA_AXI_AWADDR,
    output wire [7 : 0                      ] CAMERA_AXI_AWLEN,
    output wire [2 : 0                      ] CAMERA_AXI_AWSIZE,
    output wire [1 : 0                      ] CAMERA_AXI_AWBURST,
    output wire                               CAMERA_AXI_AWLOCK,
    output wire [2 : 0                      ] CAMERA_AXI_AWPROT,
    output wire [3 : 0                      ] CAMERA_AXI_AWCACHE,
    output wire [3 : 0                      ] CAMERA_AXI_AWQOS,
    output wire [C_M_AXI_AWUSER_WIDTH-1 : 0 ] CAMERA_AXI_AWUSER,
    output reg                                CAMERA_AXI_AWVALID,
    input  wire                               CAMERA_AXI_AWREADY,
    output wire [C_M_AXI_DATA_WIDTH-1 : 0   ] CAMERA_AXI_WDATA,
    output wire [C_M_AXI_DATA_WIDTH/8-1 : 0 ] CAMERA_AXI_WSTRB,
    output wire                               CAMERA_AXI_WLAST,
    output wire [C_M_AXI_WUSER_WIDTH-1 : 0  ] CAMERA_AXI_WUSER,
    output wire                               CAMERA_AXI_WVALID,
    input  wire                               CAMERA_AXI_WREADY,
    input  wire [C_M_AXI_ID_WIDTH-1 : 0     ] CAMERA_AXI_BID,
    input  wire [1 : 0                      ] CAMERA_AXI_BRESP,
    input  wire [C_M_AXI_BUSER_WIDTH-1 : 0  ] CAMERA_AXI_BUSER,
    input  wire                               CAMERA_AXI_BVALID,
    output wire                               CAMERA_AXI_BREADY,
    //-------------------------------------------------------- 
    input  wire                               video0_only,
    input  wire                               video1_only,
    output reg  [3:0                       ]  videos_flow_err,   
    output wire [31:0                      ]  videos_flow_cnt,   
    //-------------------------------------------------------- 
    input  wire                               camera0_video_en,
    output reg                                camera0_base_update,   // used to update base addr only
    input  wire [31:0                       ] camera0_base_addr,

    input  wire [32:0                       ] camera0_din,
    input  wire                               camera0_wr_en,
    //-------------------------------------------------------- 
    input  wire                               camera1_video_en,
    output reg                                camera1_base_update,   // used to update base addr only
    input  wire [31:0                       ] camera1_base_addr,

    input  wire [32:0                       ] camera1_din,
    input  wire                               camera1_wr_en
);  
//--------------------------------------------------------
wire clk = CAMERA_AXI_ACLK;
wire rst = ~CAMERA_AXI_ARESETN;
//--------------------------------------------------------
reg video0_only_ff;
reg video1_only_ff;
always @(posedge clk) video0_only_ff <= video0_only;
always @(posedge clk) video1_only_ff <= video1_only;
//--------------------------------------------------------
localparam VIDEO_IDLE  = 8'b00000001;
localparam VIDEO_INIT  = 8'b00000010;
localparam VIDEO_CHK0  = 8'b00000100;
localparam VIDEO_CAM0  = 8'b00001000;
localparam VIDEO_CHK1  = 8'b00010000;
localparam VIDEO_CAM1  = 8'b00100000;
localparam VIDEO_NEXT  = 8'b01000000;
//--------------------------------------------------------
reg[7:0]   cur_video_state;
reg[7:0]   nxt_video_state;
reg[3:0]   in_proc_video  ;
reg        video_turn1;
//--------------------------------------------------------
localparam IDLE        = 8'b0000_0001;
localparam UPDAT_ADDR  = 8'b0000_0010; 
localparam SEND_ADDR   = 8'b0000_0100;   
localparam SEND_DATA   = 8'b0000_1000;   
localparam UPDAT_PTR   = 8'b0001_0000;   
localparam NEXT_WRITE  = 8'b0010_0000;   
localparam SEND_PAD    = 8'b1000_0000;  
//--------------------------------------------------------
reg [7:0]   cur_proc_state;  
reg [7:0]   nxt_proc_state;  
//--------------------------------------------------------
wire        camera0_frame_last;
wire        camera0_fifo_empty;
wire        camera0_prog_full;
wire        camera0_prog_empty;
wire        camera0_underflow;
wire        camera0_overflow;

wire        camera1_frame_last;
wire        camera1_fifo_empty;
wire        camera1_prog_full;
wire        camera1_prog_empty;
wire        camera1_underflow;
wire        camera1_overflow;

wire        proc_frame_last ;
wire        proc_fifo_empty ;
wire        proc_prog_full  ;
wire        proc_prog_empty ;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_video_state <= VIDEO_IDLE;
    end 
    else begin 
        cur_video_state <= nxt_video_state;
    end 
end 
//--------------------------------------------------------
always @(*) begin
    case(cur_video_state) 
        VIDEO_IDLE: begin
            if(camera0_video_en||camera1_video_en) begin
                nxt_video_state = VIDEO_INIT;
            end
            else begin
                nxt_video_state = cur_video_state;
            end
        end
        VIDEO_INIT: begin
            if(camera0_video_en||camera1_video_en) begin
                if(video0_only_ff) begin
                    nxt_video_state = VIDEO_CHK0;
                end
                else if(video1_only_ff) begin
                    nxt_video_state = VIDEO_CHK1;
                end
                else if((~camera0_video_en)||video_turn1)begin
                    nxt_video_state = VIDEO_CHK1;
                end
                else begin
                    nxt_video_state = VIDEO_CHK0;
                end
            end
            else begin
                nxt_video_state = VIDEO_IDLE;
            end
        end        
        VIDEO_CHK0: begin
            if(camera0_video_en&(~camera0_prog_empty))begin
                nxt_video_state = VIDEO_CAM0;
            end  
            else begin
                nxt_video_state = VIDEO_NEXT;
            end      
        end
        VIDEO_CAM0: begin
            if(cur_proc_state == NEXT_WRITE) begin
                nxt_video_state = VIDEO_NEXT;
            end   
            else begin
                nxt_video_state = cur_video_state;
            end     
        end        
        VIDEO_CHK1: begin
            if(camera1_video_en&(~camera1_prog_empty)) begin
                nxt_video_state = VIDEO_CAM1;
            end  
            else begin
                nxt_video_state = VIDEO_NEXT;
            end
        end
        VIDEO_CAM1: begin
            if(cur_proc_state == NEXT_WRITE) begin
                nxt_video_state = VIDEO_NEXT;
            end   
            else begin
                nxt_video_state = cur_video_state;
            end         
        end        
        VIDEO_NEXT: begin
            nxt_video_state = VIDEO_INIT;
        end            
        default: begin
            nxt_video_state = VIDEO_IDLE;
        end
    endcase 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        video_turn1 <= 1'b0;        
    end 
    else if(cur_video_state == VIDEO_NEXT)begin 
        video_turn1 <= ~video_turn1;
    end 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        in_proc_video <= 4'h0;        
    end 
    else if(cur_video_state == VIDEO_CAM0)begin   
        in_proc_video <= 4'h0;        
    end 
    else if(cur_video_state == VIDEO_CAM1)begin   
        in_proc_video <= 4'h1;        
    end
    else begin
        in_proc_video <= 4'hf;        
    end
end 
//-------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_proc_state <= IDLE;    
    end 
    else begin 
        cur_proc_state <= nxt_proc_state;    
    end 
end 
//--------------------------------------------------------
always @(*) begin
    case(cur_proc_state)  
        IDLE: begin  
            if((cur_video_state == VIDEO_CAM0)||(cur_video_state == VIDEO_CAM1))begin
                nxt_proc_state  = SEND_ADDR; 
            end
            else  begin
                nxt_proc_state  = cur_proc_state;
            end
        end  
        SEND_ADDR: begin  
            if(CAMERA_AXI_AWREADY & CAMERA_AXI_AWVALID ) begin 
                nxt_proc_state  = SEND_DATA;  
            end
            else  begin
                nxt_proc_state  = cur_proc_state;
            end  
        end  
        SEND_DATA: begin  
            if(CAMERA_AXI_WREADY & CAMERA_AXI_WVALID & proc_frame_last)  begin
                nxt_proc_state  = CAMERA_AXI_WLAST ? UPDAT_ADDR:SEND_PAD;  
            end
            else if(CAMERA_AXI_WREADY & CAMERA_AXI_WVALID & CAMERA_AXI_WLAST ) begin 
                nxt_proc_state  = UPDAT_PTR;  
            end
            else  begin
                nxt_proc_state  = cur_proc_state;
            end 
        end  
        UPDAT_ADDR: begin
            nxt_proc_state  = UPDAT_PTR; 
        end
        UPDAT_PTR: begin  
            nxt_proc_state  = NEXT_WRITE;
        end  
        NEXT_WRITE: begin  
            nxt_proc_state  = IDLE;  
        end          
        SEND_PAD : begin
            if(CAMERA_AXI_WREADY & CAMERA_AXI_WVALID & CAMERA_AXI_WLAST) begin
                nxt_proc_state = UPDAT_ADDR;
            end
            else begin
                nxt_proc_state = SEND_PAD;
            end
        end
        default: begin  
            nxt_proc_state  = IDLE;  
        end  
    endcase  
end  
//--------------------------------------------------------
wire camera0_in_proc = (in_proc_video == 4'h0);
wire camera1_in_proc = (in_proc_video == 4'h1);
//--------------------------------------------------------
localparam FRAM_BURST_CNT = 'h4b000; // >= 'h4b000
//--------------------------------------------------------
reg[31:0] camera0_inner_frm_addr;
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera0_inner_frm_addr <= 32'b0;    
    end 
    else if(camera0_in_proc) begin 
        if((cur_proc_state == SEND_DATA)&&(CAMERA_AXI_WVALID)&&(CAMERA_AXI_WREADY)) begin 
            camera0_inner_frm_addr <= (camera0_inner_frm_addr > FRAM_BURST_CNT)?0:(camera0_inner_frm_addr + 32'b1);    
        end
        else if(cur_proc_state == UPDAT_ADDR)begin
            camera0_inner_frm_addr <= 32'b0;    
        end
    end
end 
//--------------------------------------------------------
reg[31:0] camera1_inner_frm_addr;
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera1_inner_frm_addr <= 32'b0;    
    end 
    else if(camera1_in_proc) begin
        if((cur_proc_state == SEND_DATA)&&(CAMERA_AXI_WVALID)&&(CAMERA_AXI_WREADY)) begin 
            camera1_inner_frm_addr <= (camera1_inner_frm_addr > FRAM_BURST_CNT)?0:(camera1_inner_frm_addr + 32'b1);    
        end
        else if(cur_proc_state == UPDAT_ADDR)begin
            camera1_inner_frm_addr <= 32'b0; 
        end   
    end
end 
//--------------------------------------------------------
reg[31:0] inner_pix_cntr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        inner_pix_cntr <= 32'b0;    
    end 
    else if(((cur_proc_state == SEND_DATA)||(cur_proc_state == SEND_PAD))&&(CAMERA_AXI_WVALID)&&(CAMERA_AXI_WREADY)) begin 
        inner_pix_cntr <= (inner_pix_cntr == CAMERA_AXI_AWLEN)?0:(inner_pix_cntr + 32'b1);    
    end
    else if((cur_proc_state == SEND_DATA)||(cur_proc_state == SEND_PAD))begin
        inner_pix_cntr <= inner_pix_cntr;    
    end
    else begin
        inner_pix_cntr <= 32'b0;    
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera0_base_update <= 1'h0;    
    end 
    else if(camera0_in_proc) begin
        camera0_base_update <= (cur_proc_state == UPDAT_ADDR)?1'b1:1'b0;    
    end 
end
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera1_base_update <= 1'h0;    
    end 
    else if(camera1_in_proc) begin
        camera1_base_update <= (cur_proc_state == UPDAT_ADDR)?1'b1:1'b0;    
    end 
end 
//--------------------------------------------------------
reg[31:0] camera0_cur_start_addr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera0_cur_start_addr <= 32'h0;    
    end 
    else if(cur_video_state == VIDEO_IDLE) begin
        camera0_cur_start_addr <= camera0_base_addr;    
    end
    else if((camera0_in_proc)&&(cur_proc_state == UPDAT_ADDR))begin
        camera0_cur_start_addr <= camera0_base_addr;    
    end 
end 
//--------------------------------------------------------
reg[31:0] camera1_cur_start_addr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        camera1_cur_start_addr <= 32'h0;    
    end 
    else if(cur_video_state == VIDEO_IDLE) begin
        camera1_cur_start_addr <= camera1_base_addr;    
    end    
    else if((camera1_in_proc)&&(cur_proc_state == UPDAT_ADDR))begin
        camera1_cur_start_addr <= camera1_base_addr;    
    end 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        CAMERA_AXI_AWADDR  <= 'b0;
        CAMERA_AXI_AWVALID <= 1'b0;
    end 
    else if(cur_proc_state == SEND_ADDR) begin 
        if(camera0_in_proc)begin
            CAMERA_AXI_AWADDR  <= camera0_cur_start_addr + {camera0_inner_frm_addr[29:0],2'b00};
        end
        else begin
            CAMERA_AXI_AWADDR  <= camera1_cur_start_addr + {camera1_inner_frm_addr[29:0],2'b00};
        end
        // Once asserted, VALIDs cannot be deasserted, so axi_arvalid
        // must wait until transaction is accepted  
        CAMERA_AXI_AWVALID <= (CAMERA_AXI_AWVALID&CAMERA_AXI_AWREADY)?1'b0:1'b1;
    end
    else begin
        CAMERA_AXI_AWVALID <= 1'b0;  
    end
end  
//--------------------------------------------------------
wire[32:0] camera0_fifo_dout;
wire       camera0_fifo_rd_en   = (camera0_in_proc)&&(cur_proc_state==SEND_DATA)&&(~camera0_fifo_empty)&&CAMERA_AXI_WREADY;
assign     camera0_frame_last   = camera0_fifo_dout[32];
wire       camera0_fifo_rd_clk  = CAMERA_AXI_ACLK;
//--------------------------------------------------------
wire[32:0] camera1_fifo_dout;
wire       camera1_fifo_rd_en   = (camera1_in_proc)&&(cur_proc_state==SEND_DATA)&&(~camera1_fifo_empty)&&CAMERA_AXI_WREADY;
assign     camera1_frame_last   = camera1_fifo_dout[32];
wire       camera1_fifo_rd_clk  = CAMERA_AXI_ACLK;
//--------------------------------------------------------
assign     CAMERA_AXI_WVALID = (cur_proc_state == SEND_PAD)||((cur_proc_state==SEND_DATA)&&(~proc_fifo_empty));
assign     CAMERA_AXI_WLAST  = (inner_pix_cntr == CAMERA_AXI_AWLEN)? 1'b1:1'b0;
assign     CAMERA_AXI_WDATA  = (camera0_in_proc)? camera0_fifo_dout:camera1_fifo_dout;
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst0 (   
  .rst           ( rst                        ) ,
 
  .wr_clk        ( clk                        ) ,
  .din           ( camera0_din                ) ,
  .full          ( camera0_full               ) ,
  .wr_en         ( camera0_wr_en              ) ,

  .rd_clk        ( camera0_fifo_rd_clk        ) ,
  .rd_en         ( camera0_fifo_rd_en         ) ,
  .dout          ( camera0_fifo_dout          ) ,
  .empty         ( camera0_fifo_empty         ) ,

  .prog_full     ( camera0_prog_full          ) ,
  .prog_empty    ( camera0_prog_empty         ) 
); 
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst1 (   
  .rst           ( rst                        ) ,
 
  .wr_clk        ( clk                        ) ,
  .din           ( camera1_din                ) ,
  .full          ( camera1_full               ) ,
  .wr_en         ( camera1_wr_en              ) ,

  .rd_clk        ( camera1_fifo_rd_clk        ) ,
  .rd_en         ( camera1_fifo_rd_en         ) ,
  .dout          ( camera1_fifo_dout          ) ,
  .empty         ( camera1_fifo_empty         ) ,

  .prog_full     ( camera1_prog_full          ) ,
  .prog_empty    ( camera1_prog_empty         ) 
); 
//--------------------------------------------------------
reg[15:0] camera0_frm_cntr;
reg[15:0] camera1_frm_cntr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin
        camera0_frm_cntr <= 16'b0;
        camera1_frm_cntr <= 16'b0;
    end
    else begin
        if(camera0_base_update) camera0_frm_cntr <= camera0_frm_cntr + 16'b1;
        if(camera1_base_update) camera1_frm_cntr <= camera1_frm_cntr + 16'b1;
    end
end
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin
        videos_flow_err <= 4'b0;
    end
    else begin
        videos_flow_err <= {camera1_overflow,camera1_underflow,camera0_overflow,camera0_underflow};
    end
end
//--------------------------------------------------------
assign videos_flow_cnt = {camera1_frm_cntr,camera0_frm_cntr};
//--------------------------------------------------------
function integer clogb2 (input integer bit_depth);              
    begin                                                           
    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   
        bit_depth = bit_depth >> 1;                                 
    end                                                           
endfunction   
//--------------------------------------------------------
assign proc_frame_last = (camera0_in_proc)? camera0_frame_last:camera1_frame_last;
assign proc_fifo_empty = (camera0_in_proc)? camera0_fifo_empty:camera1_fifo_empty;
assign proc_prog_full  = (camera0_in_proc)? camera0_prog_full :camera1_prog_full ;
assign proc_prog_empty = (camera0_in_proc)? camera0_prog_empty:camera1_prog_empty;
//--------------------------------------------------------
assign CAMERA_AXI_AWID    = 'b0;
assign CAMERA_AXI_AWLEN   = C_M_AXI_BURST_LEN - 1;            //burst length: 64
assign CAMERA_AXI_AWSIZE  = clogb2((C_M_AXI_DATA_WIDTH/8)-1); //Size should be C_M_AXI_DATA_WIDTH, in 2^SIZE bytes
assign CAMERA_AXI_AWBURST = 2'b01     ;                       //00:fix,01:incr,10:wrap,11:rsv
assign CAMERA_AXI_AWLOCK  = 2'b00     ;   //
assign CAMERA_AXI_AWPROT  = 3'b000    ;   //
assign CAMERA_AXI_AWCACHE = 4'b0011   ;   /////
assign CAMERA_AXI_AWQOS   = 4'b0000   ;   //
assign CAMERA_AXI_AWUSER  = 'b1       ;

assign CAMERA_AXI_WSTRB   = {(C_M_AXI_DATA_WIDTH/8){1'b1}};
assign CAMERA_AXI_WUSER   = 'b0;

assign CAMERA_AXI_ARLEN   = C_M_AXI_BURST_LEN - 1;
assign CAMERA_AXI_ARSIZE  = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
assign CAMERA_AXI_ARBURST = 2'b01;
assign CAMERA_AXI_ARCACHE = 4'b0011;

assign CAMERA_AXI_BREADY  = 'b1;

endmodule
